#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         ScoredReport.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  This file defines the ScoredReport class, a simple
#               report that uses the model component reporting
#               attributes to build a report
#
# See Also:     GenericObject, Component, Model, Section
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.SingleAssessmentReport import SingleAssessmentReport, SingleAssessmentReportInfo
from Products.KnowledgeEngine.AssessmentAttribute import AssessmentAttribute
from Products.KnowledgeEngine.AssessmentAttributeHandle import AssessmentAttributeHandle
from Products.KnowledgeEngine.AssessmentComponentValueTypes import *
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.EventDefinition import *
from Products.KnowledgeEngine.KERLExtensions import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## ScoredReportInfo Class Definition
class ScoredReportInfo(SingleAssessmentReportInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'ScoredReport'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Scored Report"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "scoredreport"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/ScoredReport_icon.gif', globals())
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        SingleAssessmentReportInfo.onConfigureProperties(self)
    
        #add the 'passScore' property        
        self.addPropertyDefinition(PropertyDefinition(identity="passScore", displayName="Pass Score", description="The score that determines a pass or fail result - a negative value turns feature off", defaultValue="-1.0", propertyType=pt_FLOAT, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC))        
    
        #add the 'includeHiddenComponents' property
        self.addPropertyDefinition(PropertyDefinition(identity="includeHiddenComponents", displayName="Include hidden components in score calculation?", defaultValue=false, description="Set this option so that when calculating the score for an assessment, components that are potentially hidden (due to showif rules) are included in the resulting score. (ignored if score calculation not required)", propertyType=pt_BOOLEAN, propertyFormat=pf_CHECKBOX))
    
        #add the 'displayPassFailStatus' property
        self.addPropertyDefinition(PropertyDefinition(identity="displayPassFailStatus", displayName="Display Pass/Fail Status?", defaultValue=true, description="This option enables the display of the Pass/Fail status (valid only when a Pass Score is defined)", propertyType=pt_BOOLEAN, propertyFormat=pf_CHECKBOX))
    
        #add the 'displayMaximumScore' property
        self.addPropertyDefinition(PropertyDefinition(identity="displayMaximumScore", displayName="Display Maximum Score?", defaultValue=true, description="This option enables the display of the maximum scores", propertyType=pt_BOOLEAN, propertyFormat=pf_CHECKBOX))
    
        #add the 'displayScorePercentage' property
        self.addPropertyDefinition(PropertyDefinition(identity="displayScorePercentage", displayName="Display Score Percentage?", defaultValue=true, description="This option enables the display of the score as a percentage of the maximum", propertyType=pt_BOOLEAN, propertyFormat=pf_CHECKBOX))
    
    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
                
        #initialise the super-class events for reflection
        SingleAssessmentReportInfo.onConfigureEvents(self)

    #---------------------------------------------------------------#

#####################################################################
## ScoredReport Class Definition
class ScoredReport(SingleAssessmentReport, KERLExtensions):
    """
        A class to represent a simple report that uses the components
        within the containing Model to build a report
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = ScoredReportInfo()

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the object"        

        SingleAssessmentReport.__init__(self, id, identity)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation for the parent
        SingleAssessmentReport.onAfterInstanciation(self)

        #add passScore attribute if it doesn't already exist
        if not hasattr(self, "passScore"):
            self.passScore = "-1"

        #add includeHiddenComponents attribute if it doesn't already exist
        if not hasattr(self, "includeHiddenComponents"):
            self.includeHiddenComponents = false

        #add displayPassFailStatus attribute if it doesn't already exist
        if not hasattr(self, "displayPassFailStatus"):
            self.displayPassFailStatus = true

        #add displayMaximumScore attribute if it doesn't already exist
        if not hasattr(self, "displayMaximumScore"):
            self.displayMaximumScore = true

        #add displayScorePercentage attribute if it doesn't already exist
        if not hasattr(self, "displayScorePercentage"):
            self.displayScorePercentage = true

    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        """
            Handle Knowledge Engine specific actions before this object is deleted
        """

        #handle the superclass requirements
        SingleAssessmentReport.onBeforeDelete(self)

        #create an assessment attribute handle that represents the scores for report
        scoreAttributeHandle = AssessmentAttributeHandle(self.getIdentity() + ":score", st_FLOAT, self.getModel().getIdentity()) 

        #get the assessment storage service
        assessmentStorageService = self.getModel().getAssessmentStorageService()
        
        #now delete all of the score attributes as this report is about to be destroyed.
        assessmentStorageService.deleteAllAttributes(scoreAttributeHandle)

    #---------------------------------------------------------------#

    def getPassScore(self):
        """
            Returns the Pass Score of the report
        """
        
        return self.passScore  

    #---------------------------------------------------------------#
        
    def setPassScore(self, passScore):
        """
            Sets the Pass Score of the report
        """
        
        self.passScore = passScore

    #---------------------------------------------------------------#

    def hasPassScore(self):
        """
            Returns the if the Pass Score is being used
        """
        
        return self.passScore >= 0 

    #---------------------------------------------------------------#

    def hiddenComponentsContributeToScore(self):
        """
            Returns if hidden components contribute to the score
        """
        
        return self.includeHiddenComponents

    #---------------------------------------------------------------#

    def shouldDisplayPassFailStatus(self):
        """
            Returns if pass/fail status should be displayed (in the myknowledge area)
        """
        
        return self.displayPassFailStatus

    #---------------------------------------------------------------#

    def shouldDisplayMaximumScore(self):
        """
            Returns if maximum score should be displayed (in the myknowledge area)
        """
        
        return self.displayMaximumScore

    #---------------------------------------------------------------#

    def shouldDisplayScorePercentage(self):
        """
            Returns if score percentage should be displayed (in the myknowledge area)
        """
        
        return self.displayScorePercentage
        
    #---------------------------------------------------------------#

    def renderReportFor(self, genericObject, request, **args):
        """
            Renders the report content for the specified generic object
            and the type of report
        """

        #time to render the report        
        if genericObject.isInstanceOf('Scorable') or genericObject.isContainer():

            #get the super-class to generate the report
            return SingleAssessmentReport.renderReportFor(self, genericObject, request, **args)
    
    #---------------------------------------------------------------#
        
    def getRawScoreForComponent(self, component, assessment):
        """
            PRIVATE: Returns the raw score for the specified component in 
            the provided assessment.
            
            NOTE: the raw score doesn't take into account
            things like component wieghtings or showif but does
            take into account if the component has a computeScore attribute
        """
        
        #let's assume the score is not available... 
        rawScore = 0
        
        #based on the type of component, determine the raw score     
        #------------------------
        if component.isInstanceOf("CheckBox") and component.computeScore:
            componentState = assessment.getComponentState(component.getIdentity()).getValue()
            if componentState == true:
                rawScore = component.scoreTrue
            elif componentState == false:
                rawScore = component.scoreFalse
            else:
                rawScore = component.scoreUndefined

        #------------------------
        elif component.isInstanceOf("MultipleChoice") and component.computeScore:
            componentState = assessment.getComponentState(component.getIdentity()).getValue()
            answer = component.getAnswer(componentState)
            if answer is not None:
                rawScore = answer.getScore()
            
        #------------------------
        elif component.isInstanceOf("Option") and component.computeScore:
            componentState = assessment.getComponentState(component.getIdentity()).getValue()
            if componentState == true:
                rawScore = component.scoreTrue           
            else:
                rawScore = component.scoreFalse  
            
        return rawScore
    
    #---------------------------------------------------------------#
        
    def getWeightingForComponent(self, component):
        """
            PRIVATE: Returns the raw score wieghting for the specified component
        """
        
        #let's assume the weighting is not available... 
        weighting = 1.0
        
        #based on the type of component, determine the weighting     
        if component.isInstanceOf("CheckBox"):
            weighting = component.getWeighting()

        elif component.isInstanceOf("MultipleChoice"):
            weighting = component.getWeighting()
            
        elif component.isInstanceOf("Option"):
            weighting = component.getWeighting()
            
        return weighting
    
    #---------------------------------------------------------------#
        
    def getMaximumPossibleScoreForComponent(self, component):
        """
            PRIVATE: Returns the maximum possible score for the specified component
            taking into account weightings and showif!
        """
        
        #let's assume the maximum score is not available... 
        maximumScore = 0.0

        #is the component a real component without showif?
        if component.isInstanceOf("Component") and ((component.hasShowIf() and self.hiddenComponentsContributeToScore()) or not component.hasShowIf()):

            #based on the type of component, determine the raw score    
            #------------------
            if component.isInstanceOf("CheckBox") and component.computeScore:

                #the maximum for a checkbox is the greatest of the true, false and undefined selection scores
                maximumScore = max([component.scoreTrue, component.scoreFalse, component.scoreUndefined]) * self.getWeightingForComponent(component)

            #------------------
            elif component.isInstanceOf("MultipleChoice") and component.computeScore:
                #the maximum of a multiple choice is the greatest answer score
                maximumScore = max([answer.getScore() for answer in component.getAnswerList()]) * self.getWeightingForComponent(component)

            #------------------
            elif component.isInstanceOf("Selection"):
                #get the options
                options = component.getChildren()

                #we only want options that are showable AND have a computeable score
                options = [option for option in options if option.computeScore and ((option.hasShowIf() and self.hiddenComponentsContributeToScore()) or not option.hasShowIf())]
                
                #how many do we have?
                if len(options) == 0:
                    #there is no score for no options!
                    maximumScore = 0.0

                elif len(options) == 1:
                    #the score for a single option is the maximum type of selection
                    maximumScore = max([options[0].scoreTrue, options[0].scoreFalse]) * self.getWeightingForComponent(options[0])
            
                #the maximum of a selection depends on how the selection is rendered
                elif component.getDisplayFormat() == "Radio Buttons" or component.getDisplayFormat() == "Dropdown List":
                    #as there can only be either no selections or one selection, the maximum is the max of these
                    #scores                    
                    noSelectionScore = sum([option.scoreFalse * self.getWeightingForComponent(option) for option in options])

                    #determine the best selection from the options which is the option where by 
                    #it's selected score plus the other options false scores is greater than all other selected options
                    oneSelectionScore = max([(option.scoreTrue * self.getWeightingForComponent(option)) + sum([other.scoreFalse * self.getWeightingForComponent(other) for other in options if other.getIdentity() != option.getIdentity()]) for option in options])                    

                    #the maximum is between the noselection and making a selection
                    maximumScore = max([noSelectionScore, oneSelectionScore])

                elif component.getDisplayFormat() == "Check Boxes":
                    #as there can be many selections, the maximum is the sum of the greatest option values
                    maximumScore = sum([max([option.scoreTrue, option.scoreFalse]) * self.getWeightingForComponent(option) for option in options])

                else:
                    #unknown display format... so the selection has no effect on the score
                    maximumScore = 0.0

            #------------------
            elif component.isInstanceOf("RandomizedSection"):
                #the maximum is the sum of the top n components in the randomized section, 
                #where n is the number of comoponents displayed in the section
                childScores = [self.getMaximumPossibleScoreForComponent(child) for child in component.getChildren()]
                
                #sort the childscores (highest first)
                childScores.sort(lambda x, y : x < y)
                
                #take the top n childscores
                childScores = childScores[0:component.getNrComponentsToDisplay()]

                #return the sum of the top child scores possible 
                maximumScore = sum(childScores)

            #------------------
            elif component.isContainer():
                #catch all... the maximum is the sum of the child score maximums
                maximumScore = sum([self.getMaximumPossibleScoreForComponent(child) for child in component.getChildren()])
        
        return maximumScore
    
    #---------------------------------------------------------------#
        
    def getMaximumPossibleScore(self):
        """
            Return the maximum possible score for the report in 
            the model
        """            

        #should we recalculate the score? assume yes!
        mustRecalculateMaximumScore = true
        maximumPossibleScore = 0.0
        cacheOnceCalculated = false

        #has the model been locked for editing?
        if self.getModel().isLockedForEditing():
            if hasattr(self, 'maximumPossibleScore') and self.maximumPossibleScore is not None:
                #get the cached version (if available)
                maximumPossibleScore = self.maximumPossibleScore
                
                #no need to recalculate as we have a copy
                mustRecalculateMaximumScore = false
            else:
                #it's missing, so let's recalculate
                mustRecalculateMaximumScore = true
                cacheOnceCalculated = true
        else:
            #we always recalculate the score if the model is unlocked
            mustRecalculateMaximumScore = true
            
            #make sure we don't have a cached version
            self.maximumPossibleScore = None
            
        #should we recalculate
        if mustRecalculateMaximumScore:
            maximumPossibleScore = sum([self.getMaximumPossibleScoreForComponent(child) for child in self.getModel().getComponentContainers()])

        #should we now cache?
        if cacheOnceCalculated:
            self.maximumPossibleScore = maximumPossibleScore
            
        return maximumPossibleScore            
        
    #---------------------------------------------------------------#

    def getScoreForComponent(self, component, assessment):
        """
            Returns the score that the specified component contributes
            to the total score for the provide assessment using this
            report, correctly taking showif and component weightings 
            into account.          
        """

        #let's assume no score contribution
        score = 0.0

        #is the component a real component without showif?
        if component.isInstanceOf("Component") and ((component.hasShowIf() and self.hiddenComponentsContributeToScore()) or not component.hasShowIf()):

            #are we dealing with a randomised section? (we need to only sum the displayed child components)
            if component.isInstanceOf("RandomizedSection"):
                for child in component.getRandomizedChildren(assessment):
                    score = score + self.getScoreForComponent(child, assessment)

            #the score for a container is the sum of the contributing scores of the children
            elif component.isContainer():
                for child in component.getChildren():
                    score = score + self.getScoreForComponent(child, assessment)
            
            #for other components, use the raw score with the component weighting
            else:
                score = score + (self.getRawScoreForComponent(component, assessment) * self.getWeightingForComponent(component)) 
        
        return score
        
    #---------------------------------------------------------------#
        
    def getScore(self, assessment):
        """
            Return the score for the provided assessment
        """

        #assume we have to calculate the score
        mustRecalculateScore = true
        mustCacheScoreOnceCalculated = false
        score = 0.0

        #create an assessment attribute handle that represents the score for this assessment and report
        scoreAttributeHandle = AssessmentAttributeHandle(self.getIdentity() + ":score", st_FLOAT, self.getModel().getIdentity()) 
        scoreAttribute = None
        
        #has the model been locked for editing?
        if self.getModel().isLockedForEditing():
            #attempt to get the attribute for the assessment
            scoreAttribute = assessment.getAttribute(scoreAttributeHandle)

            #does the attribute exist? we may need to create it
            if scoreAttribute is None:
                mustRecalculateScore = true
                mustCacheScoreOnceCalculated = true

            #we already have a score... is it valid? (the score generated after the model was locked for editing)
            elif self.getModel().getLockedForEditingOn().lessThan(assessment.getInstantModified()) and assessment.getInstantModified().lessThanEqualTo(scoreAttribute.getInstantModified()):
                score = scoreAttribute.getValue()
                mustRecalculateScore = false
                mustCacheScoreOnceCalculated = false

            #the score is out of date... we need to recalculate
            else:
                mustRecalculateScore = true
                mustCacheScoreOnceCalculated = true

        #we must always recalculate if we are not locked                
        else:            
            mustRecalculateScore = true
            mustCacheScoreOnceCalculated = false

        #we need to recalculate?        
        if mustRecalculateScore:
            #calculate the score (only if necessary)
            #sum the local scores for the component containers in the model
            for component in self.getModel().getComponentContainers():                   
                #add the component container local scores and maximum to the ultimate score and maximum
                score = score + self.getScoreForComponent(component, assessment)

        #we need to update the cache?       
        if mustCacheScoreOnceCalculated:
            #do we need to create a score attribute
            if scoreAttribute is None:
                scoreAttribute = AssessmentAttribute(scoreAttributeHandle, assessment.getIdentity(), score)
            else:
                scoreAttribute.setValue(score)
                
            assessment.setAttribute(scoreAttribute)
            assessment.getStorageService().saveAssessment(assessment)
            
        return score
                
    #---------------------------------------------------------------#

    def getReportTitleForAssessment(self, assessment):
        """
            Returns the report title for the specified assessment
            so that it may be displayed in areas like the 
            MyKnowledge Area and/or the AssessmentExplorer.
            
            NOTE: This method may be overriden to enable
            customised report titles.  eg: generating a title
            that contains a score or pass/fail.
        """
        
        #get the score for the assessment
        score = self.getScore(assessment)
        scorePart = "%.2f" % score

        #get the maximum possible score for the report
        maximumScore = self.getMaximumPossibleScore()
        
        #determine the percentage (if we have a maximum)
        maximumScorePart = ""
        if maximumScore > 0:
            if self.shouldDisplayMaximumScore():
                maximumScorePart = maximumScorePart + " out of %.2f" % (maximumScore, )
                
            if self.shouldDisplayScorePercentage():
                percentage = score / maximumScore * 100.0
                maximumScorePart = maximumScorePart + " : %.2f%%" % (percentage, )
        
        #determine pass/fail
        passFailPart = ""
        if self.hasPassScore() and self.shouldDisplayPassFailStatus():
            passFailPart = " : %s" % iif(score >= self.getPassScore(), "<span class='passingScore'>Pass</span>", "<span class='failingScore'>Fail</span>")
            
        #return the title of the report with the score
        return "%s (score of %s%s%s)" % (self.getTitleOrId(), scorePart, maximumScorePart, passFailPart)

    #---------------------------------------------------------------#
    
    #---------------------------------------------------------------#
    # BEGIN: KERLExtensions Implementation                          #
    #---------------------------------------------------------------#

    def compileKERLtoJavaScript(self, expression):
        """
            Returns a Python string that represents a JavaScript
            expression, which may be used to evaluate the status
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.
            
            RETURN TYPE: String         
        """
        
        #TODO: one day we might make client-side KERL scoring work
        #this would require the page that we are currently on to work out the dynamic parts of the assessment
        return ""

    #---------------------------------------------------------------#

    def compileKERLtoEnglish(self, expression):
        """
            Returns a string representing an 'English' version
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            English descriptions of KERL expressions that may be 
            used to describe things like AssessmentFilters
            
            RETURN TYPE: String         
        """
        
        #determine the type of KERL expression
        if expression[1] == 'pass':       
            return "Assessments Passed (according to %s)" % self.getTitleOrId()

        elif expression[1] == 'fail':       
            return "Assessments Failed (according to %s)" % self.getTitleOrId()

        elif expression[1] == 'score' or expression[1] == 'componentscore':
            #determine the score, operator and value
            if expression[1] == 'score':
                score = "Score"
                operator = expression[2]
                value = float(expression[3])
            else:
                #get the score for the component
                component = assessment.getModel().getComponent(expression[2])
                if component is not None:
                    score = "Score for %s" % (component.getTitleOrId())
                    operator = expression[3]
                    value = float(expression[4])
                else:
                    #not a scoreable component
                    score = "unknown"
                    operator = "unknown"
                    value = 0.0
            
            #now test the value
            if operator in ['=', '==', 'equal', 'equals']:
                return "Assessment %s equals %.2f" % (score, value)
            elif operator in ['!=', 'notequal', 'notequals']:
                return "Assessment %s not equals %.2f" % (score, value)
            elif operator in ['>', 'greaterthan']:
                return "Assessment %s greater than %.2f" % (score, value)
            elif operator in ['>=', '=>', 'greaterthanorequaltoo']:
                return "Assessment %s greater than or equal to %.2f" % (score, value)
            elif operator in ['<', 'lessthan']:
                return "Assessment %s less than %.2f" % (score, value)
            elif operator in ['<=', '=<', 'lessthanorequaltoo']:
                return "Assessment %s less than or equal to %.2f" % (score, value)
            else:
                return "(unknown ScoredReport KERL score expression)"

        elif expression[1] == 'scoreinrange':       
            return "Assessment Scores in range %.2f ... %.2f (according to %s)" % (float(expression[2]), float(expression[3]), self.getTitleOrId())

        else:
            return "(unknown ScoredReport KERL expression)"

    #---------------------------------------------------------------#

    def evaluate(self, expression, assessment):
        """
            Evalutes the component class specific KERL expression
            using the provided assessment.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.

            RETURN TYPE: Boolean         
        """
        
        #Scored Reports have KERLS of the following form
        #['scoredreport1234', 'score', operator, value]
        #['scoredreport1234', 'componentscore', 'ReadableScoreComponentId', operator, value]
        #['scoredreport1234', 'pass']
        #['scoredreport1234', 'fail']
        #['scoredreport1234', 'scoreinrange', fromValue, toValue]  (toValue not inclusive!)
        
        #determine the type of kerl operator
        if expression[1] in ['pass', 'fail']:       
            if self.hasPassScore():
                #calculate the score
                score = self.getScore(assessment)
            
                #does the assessment pass?
                return (expression[1] == 'pass' and score >= self.getPassScore()) or (expression[1] == 'fail' and score < self.getPassScore())
                
            else:
                #no pass score... so must fail
                return false
                
        elif expression[1] == 'score' or expression[1] == 'componentscore':
            
            #determine the score, operator and value
            if expression[1] == 'score':
                score = self.getScore(assessment)
                operator = expression[2]
                value = float(expression[3])
            else:
                #get the score for the component
                component = assessment.getModel().getComponent(expression[2])
                if component is not None:
                    score = self.getScoreForComponent(component, assessment)
                    operator = expression[3]
                    value = float(expression[4])
                else:
                    #not a scoreable component
                    score = 0.0
                    operator = "unknown"
                    value = 0.0
            
            #now test the value
            if operator in ['=', '==', 'equal', 'equals']:
                return score == value
            elif operator in ['!=', 'notequal', 'notequals']:
                return score != value
            elif operator in ['>', 'greaterthan']:
                return score > value
            elif operator in ['>=', '=>', 'greaterthanorequaltoo']:
                return score >= value
            elif operator in ['<', 'lessthan']:
                return score > value
            elif operator in ['<=', '=<', 'lessthanorequaltoo']:
                return score <= value
            else:
                return false

        elif expression[1] == 'scoreinrange':
            score = self.getScore(assessment)
            fromValue = float(expression[2])
            toValue = float(expression[3])
            return score >= fromValue and score < toValue
            
        else:
            #some unknown score operator
            return false
    
    #---------------------------------------------------------------#

    def getDependencies(self, expression):
        """
            Returns a list of components that are required to
            evaluate the component class specific KERL expression.

            NOTE: Includes the specific component itself.

            RETURN TYPE: {Component} indexed by Component.getIdentity()
        """

        #is the expression for a component score?
        if expression[1] == 'componentscore':
            referencedComponent = self.getModel().getComponent(expression[2])
            if referencedComponent is not None:
                return {self.getIdentity():self, referencedComponent.getIdentity():referencedComponent}            
            else:
                return {self.getIdentity():self}
        else:
            return {self.getIdentity():self}
        
    #---------------------------------------------------------------#

    def updateKERLIdentities(self, expression, identityMappings):
        """
            Returns the expression with the appropriate old identities
            (as identified by the identityMappings) replaced with 
            new identities.
        """

        #get the previous scored report identity
        previousScoredReportIdentity = expression[0]
        
        #does the scored report have a new identity?
        if identityMappings.has_key(previousScoredReportIdentity):
            #get the new component identity
            newScoredReportIdentity = identityMappings[previousScoredReportIdentity]
        else:
            #no new identity 
            #(we still have to resolve mappings as the component rule 
            # may refer to other components)
            newScoredReportIdentity = previousScoredReportIdentity

        #handle pass/fail operators
        if expression[1] in ['pass', 'fail']:       
            return [newScoredReportIdentity, expression[1]]
             
        #handle component score operator             
        elif expression[1] == 'componentscore':
            #map the previous component identity into a new identity
            previousComponentIdentity = expression[2]
            if identityMappings.has_key(previousComponentIdentity):
                newComponentIdentity = identityMappings[previousComponentIdentity]
            else:
                newComponentIdentity = previousComponentIdentity
        
            return [newScoredReportIdentity, expression[1], newComponentIdentity, expression[3], expression[4]]

        #handle assessment score operator
        elif expression[1] == 'score':
            return [newScoredReportIdentity, expression[1], expression[2], expression[3]]

        #handle assessment scoreinrange operator
        elif expression[1] == 'scoreinrange':
            return [newScoredReportIdentity, expression[1], expression[2], expression[3]]
            
        else:
            #unknown
            return expression

    #---------------------------------------------------------------#
    # END: KERLExtensions Implementation                            #
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

# Register Class Information with the Knowledge Engine
registerGenericObjectClass(ScoredReport)

#####################################################################
